#define _EKOS_KERNEL_C_

#include <kernel/kernel.h>

#include <inc/spinlock.h>

// Global descriptor table.
//
// The kernel and user segments are identical (except for the DPL).
// To load the SS register, the CPL must equal the DPL.  Thus,
// we must duplicate the segments for the user and the kernel.
//
struct segdesc_t gdt[GDT_COUNT] =
{
     // 0x0 - unused (always faults -- for trapping NULL far pointers)
     SEG_NULL,

     // 0x8 - kernel code segment
     [GD_KERN_TEXT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0),

     // 0x10 - kernel data segment
     [GD_KERN_DATA >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0),

     // 0x18 - user code segment
     [GD_USER_TEXT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3),

     // 0x20 - user data segment
     [GD_USER_DATA >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),

     // 0x28 - tss, not initialized now
     [GD_TSS >> 3] = SEG_NULL
};

struct pseudodesc_t gdt_pd = {
     sizeof(gdt) - 1, (unsigned long) gdt
};

uint32_t   mlayout_count;
physaddr_t mlayout_addr[MEMMAP_MAXCOUNT << 1];

/* ============================================================ */
/* Frame Management                                             */
/* ============================================================ */

struct frame_t *frame_free_head;
struct frame_t *frame_sfree_head;

size_t          frames_count;
struct frame_t *frames;

static inline uint32_t
frame2ppn(struct frame_t *fr)
{
     return fr - frames;
}

static inline physaddr_t
frame2pa(struct frame_t *fr)
{
     return frame2ppn(fr) << FRAME_SHIFT;
}

static inline struct frame_t *
pa2frame(physaddr_t pa)
{     
     if (FRAME_NUM(pa) >= frames_count)
     {
		  intr_disable_begin();
		  
		  kprintf("painc : pa %x\n", pa);
		  // kpanic("pa2frame called with invalid pa");
		  monitor(NULL);
     }
     return &frames[FRAME_NUM(pa)];
}

static inline void*
frame2kva(struct frame_t *pp)
{
     return KADDR(frame2pa(pp));
}
     
/* 二分查找地址对应的内存映射段, 返回第一个超过指定地址的分界线 */
int
mlayout_bin_search(physaddr_t addr)
{
     int l = 0, r = mlayout_count - 1, m;

     while (l < r)
     {
		  if (mlayout_addr[m = ((l + r) >> 1)] > addr)
			   r = m;
		  else l = m + 1;
     }

     return l;
}

/* 初始化单个物理页面 */
inline static int
frame_init(struct frame_t *fr)
{
     memset(fr, 0, sizeof(struct frame_t));
	 fr->flag = FRAME_USED;
}

int
frame_alloc_unsafe(physaddr_t *addr)
{
	 if (frame_free_head == NULL)
	 {
		  sl_lock(&envs_switch_lock);
		  frame_free_head = frame_sfree_head;
		  frame_sfree_head = NULL;
		  sl_release(&envs_switch_lock);
	 }
	 
     if (frame_free_head == NULL) return -E_NO_MEM;
	 
     struct frame_t *result = frame_free_head;
     *addr = frame2pa(result);

     frame_free_head = frame_free_head->next;
     frame_init(result);
     
     return 0;
}

void
frame_free_unsafe(physaddr_t addr)
{
     struct frame_t *frame = pa2frame(addr);

	 frame->flag = FRAME_FREE;
     frame->next = frame_free_head;
     frame_free_head = frame;
}

void
frame_sfree(physaddr_t addr)
{
     struct frame_t *frame = pa2frame(addr);

	 frame->flag = FRAME_FREE;
     frame->next = frame_sfree_head;
     frame_sfree_head = frame;
}

int
frame_share_unsafe(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 if (frame->flag == FRAME_FREE)
		  return -1;

	 frame->flag = FRAME_SHARED;
	 return 0;
}

int
frame_unshare_unsafe(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 if (frame->flag == FRAME_FREE)
		  return -1;

	 frame->flag = FRAME_USED;
	 return 0;
}

int
frame_inc_ref(physaddr_t addr)
{
	 int err;
	 struct frame_t *frame = pa2frame(addr);
	 /* lock the frame to ensure the counter is correct */
	 sl_spinlock(&frame->lock);
	 if (frame->flag == FRAME_FREE)
		  err = -1;
	 else
	 {
		  err = 0;
		  ++ frame->ref_count;
     }
     sl_release(&frame->lock);
     
     return err;
}

int
frame_add_ref(physaddr_t addr, int delta)
{
	 int err;
	 struct frame_t *frame = pa2frame(addr);
	 /* lock the frame to ensure the counter is correct */
	 sl_spinlock(&frame->lock);
	 if (frame->flag == FRAME_FREE)
		  err = -1;
	 else
	 {
		  err = 0;
		  frame->ref_count += delta;
     }
     sl_release(&frame->lock);

	 if (frame->ref_count == 0)
     {
		  frame_free(addr);
     }
     
     return err;
}


int
frame_ref(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 /* lock the frame to ensure the counter is correct */
	 sl_spinlock(&frame->lock);
	 int result = frame->ref_count;
	 sl_release(&frame->lock);
     
	 return result;
}

int
frame_dec_ref(physaddr_t addr)
{
	 int err;
     struct frame_t *frame = pa2frame(addr);
     sl_spinlock(&frame->lock);
     
     uint32_t ref_count;
     if (frame->flag == FRAME_FREE)
     {
		  err = -1;
		  ref_count = -1;
     }
     else
     {
		  err = 0;
		  ref_count = 
			   -- frame->ref_count;
	 }
     sl_release(&frame->lock);

     if (ref_count == 0)
     {
		  frame_free(addr);
     }
     
     return err;
}

int
frame_share(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 sl_spinlock(&frame->lock);
	 int result = frame_share_unsafe(addr);
	 sl_release(&frame->lock);
	
	 return result;
}

int
frame_unshare(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 sl_spinlock(&frame->lock);
	 int result = frame_unshare_unsafe(addr);
	 sl_release(&frame->lock);
	
	 return result;
}

bool
frame_shared_p(physaddr_t addr)
{
	 struct frame_t *frame = pa2frame(addr);
	 sl_spinlock(&frame->lock);
	 bool result = frame->flag == FRAME_SHARED;
	 sl_release(&frame->lock);
	 return result;
}

/* 物理内存处理控制锁 */
static uint8_t frame_lock;

int
frame_alloc(physaddr_t *addr)
{
     sl_spinlock(&frame_lock);
	 
#ifdef K_MEM_DEBUG
     kprintf("[frame_alloc]", *addr);
#endif

     int result = frame_alloc_unsafe(addr);
     
#ifdef K_MEM_DEBUG
     kprintf(" result = %p\n", *addr);
#endif

     sl_release(&frame_lock);

     return result;
}

/* work with PCS mechanism */
int
frame_alloc_push(physaddr_t *addr)
{
     int result;
     if ((result = frame_alloc(addr)) == 0)
		  if ((result = pcs_push_frame(*addr)) != 0)
			   frame_free(*addr);

     frame_inc_ref(*addr);

     return result;
}

void
frame_free(physaddr_t addr)
{
     sl_spinlock(&frame_lock);
     frame_free_unsafe(addr);
     sl_release(&frame_lock);
}

/* ============================================================ */
/* Page Management                                              */
/* ============================================================ */

inline uint32_t
lock_pde(uint32_t idx)
{
	 uint32_t result;
	 while ((result = xchg32(vpd + idx, PTE_LOCK)) == PTE_LOCK);
	 tlbflush();
	 
	 return result;
}

inline void
release_pde(uint32_t idx, uint32_t val)
{
	 xchg32(vpd + idx, val);
	 tlbflush();
}
	 
inline uint32_t
lock_pte(uint32_t idx)
{
	 uint32_t result;
	 while ((result = xchg32(vpt + idx, PTE_LOCK)) == PTE_LOCK);
	 tlbflush();
	 
	 return result;
}

inline void
release_pte(uint32_t idx, uint32_t val)
{	 
	 xchg32(vpt + idx, val);
	 tlbflush();
}

physaddr_t
va2pa(void *addr)
{
	 if ((vpd[PAGEDIR_IDX(addr)] & PTE_P) &&
		 (vpt[PAGE_NUM(addr)] & PTE_P))
		  return PTE_ADDR(vpt[PAGE_NUM(addr)]) | PAGE_OFF(addr);
	 else return 0;
}

/* 清理 COW 标记 */
/* 将打上 COW 标记的页面入口进行操作 */
/* 前提 -- VPT 入口已经设置正确 */
/* addr -- 需要被修复的地址, 有可能有页面的偏移地址  */
/* create -- 如果该地址不为空, 表示需要如果页面不存在则需要创建, 权限为其值 */
/* ... -- 可选参数, 如果 create 不为空, 则后一个参数表示是否强迫创建/替换 */
int
fix_addr(void *addr, uint32_t *create, ...)
{
	 
	 uint32_t idx;
	 idx = PAGEDIR_IDX(addr);

	 uint32_t create_rep;

	 int err;
	 physaddr_t naddr;

	 uint32_t i;
	 int force;

#ifdef K_MEM_DEBUG
	 kprintf("[fix_addr] addr = %p, create = %p", addr, create);	 
#endif
	 

	 if (create != NULL)
	 {
		  va_list va;

		  va_start(va, create);
		  force = va_arg(va, int);
		  va_end(va);
		  
#ifdef K_MEM_DEBUG
		  kprintf("(%08x), force = %d. ", *create, force);
#endif
	 }
	 else
	 {
		  force = false;
		  
#ifdef K_MEM_DEBUG
		  kprintf(". ");
#endif		  
	 }
	 
	 /* 判断需要修复页表的层次 */
	 if (idx == PAGEDIR_IDX(KVPT))
	 {

#ifdef K_MEM_DEBUG
		  kprintf("[L1]");
#endif
		  
		  /* 如果是一级页表的标记需要修复 */
		  /* 真正的索引需要转换 */
		  idx = PAGETAB_IDX(addr);

		  uint32_t pde;
		  pde = lock_pde(idx);

		  ptab_entry_t *svpt;
		  ptab_entry_t *tvpt;

		  if (force)
		  {
			   create_rep = pde;
			   goto fa_create_1;
		  }
		  else if (pde & PTE_P)
		  {
			   if (!(pde & PTE_COW))
			   {
					err = 0;
					goto fa_exit_1;
			   }

			   if ((err = frame_alloc(&naddr)) != 0)
			   {
					goto fa_exit_1;
			   } else frame_inc_ref(naddr);
			   
#ifdef K_MEM_DEBUG
			   kprintf("[fix_addr] process COW sign.\n");
#endif
			   vpt[PAGE_NUM(tvpt = get_local_tvpt())] = naddr | PTE_W | PTE_P;
			   vpt[PAGE_NUM(svpt = get_local_tpage())] = PTE_ADDR(pde) | PTE_P;

			   tlbflush();

			   /* 复制页表中的项, 并打上合适的标记 */
			   for (i = 0; i != PAGETAB_COUNT; ++i)
			   {
					if (svpt[i] & PTE_P)
					{
						 if ((svpt[i] & (PTE_W | PTE_COW)) && !(svpt[i] & PTE_SHARED))
						 {
							  tvpt[i] = (svpt[i] | PTE_COW) & ~(uint32_t)PTE_W;
						 
							  /* 不能改变源 */
							  // svpt[i] = tvpt[i];
						 }
						 else
						 {
							  tvpt[i] = svpt[i];
						 }

						 frame_inc_ref(PTE_ADDR(tvpt[i]));
					} else tvpt[i] = 0;
			   }

			   frame_dec_ref(PTE_ADDR(pde));
			   pde = (naddr | (pde & PTE_USER) | PTE_W) & ~(uint32_t)PTE_COW;

			   err = 0;
		  }
		  else if (create != NULL)
		  {
			   create_rep = 0;

		  fa_create_1:
			   
			   if (PTE_ADDR(*create))
			   {
					pde = *create | PTE_P;
					*create = create_rep;

					err = 0;
			   }
			   else
			   {
					/* 创建空一级表 */
					if ((err = frame_alloc(&naddr)) != 0)
					{
						 goto fa_exit_1;
					} else frame_inc_ref(naddr);

					vpt[PAGE_NUM(tvpt = get_local_tvpt())] = naddr | PTE_W | PTE_P;
					tlbflush();
					
					memset(tvpt, 0, PAGE_SIZE);

					pde = naddr | (*create & PTE_USER) | PTE_P;
					*create = create_rep;
					
					err = 0;
			   }
			   
#ifdef K_MEM_DEBUG
			   kprintf("CREATED");
#endif
			   
		  }
		  else err = -E_FAULT;

	 fa_exit_1:

#ifdef K_MEM_DEBUG
		  kprintf("\n");
#endif

		  release_pde(idx, pde);
	 }
	 else
	 {
		  
#ifdef K_MEM_DEBUG
		  kprintf("[L2]");
#endif
		  uint32_t pte;

		  char *tspage;
		  char *tdpage;

		  if (create != NULL)
		  {
			   int cp;
			   cp = *create & PTE_USER;

			   if ((err = fix_addr((void *)(vpt + PAGE_NUM(addr)), &cp, false)) != 0)
					goto fa_exit;
		  }
		  else
		  {
			   if ((err = fix_addr((void *)(vpt + PAGE_NUM(addr)), NULL)) != 0)
					goto fa_exit;
		  }
		  
		  /* 获得索引并锁定 */
		  idx = PAGE_NUM(addr);
		  pte = lock_pte(idx);

		  if (force)
		  {
			   create_rep = pte;
			   goto fa_create_2;
		  }
		  if (pte & PTE_P)
		  {

			   if (!(pte & PTE_COW))
			   {
					err = 0;
					goto fa_exit_2;
			   }

#ifdef K_MEM_DEBUG
			   kprintf("[fix_addr] process COW sign.\n");
#endif
			   
			   if ((err = frame_alloc(&naddr)) != 0)
			   {
					goto fa_exit_2;
			   } else frame_inc_ref(naddr);

			   // here comes a little hack, since we do not use tvpt here(while processing LV2 VPT).
			   vpt[PAGE_NUM(tdpage = get_local_tvpt())] = naddr | PTE_W | PTE_P;
			   vpt[PAGE_NUM(tspage = get_local_tpage())] = PTE_ADDR(pte) | PTE_W | PTE_P;
			   tlbflush();

			   /* 复制页面 */
			   memmove(tdpage, tspage, PAGE_SIZE);

			   frame_dec_ref(PTE_ADDR(pte));
			   
			   pte = (naddr | (pte & PTE_USER) | PTE_W) & ~(uint32_t)PTE_COW;

			   err = 0;
		  }
		  else if (create != NULL)
		  {
			   create_rep = 0;

		  fa_create_2:
			   
			   if (PTE_ADDR(*create))
			   {
					pte = *create | PTE_P;
					*create = create_rep;

					err = 0;
			   }
			   else
			   {
					if ((err = frame_alloc(&naddr)) != 0)
					{
						 goto fa_exit_2;
					} else frame_inc_ref(naddr);

					pte = naddr | (*create & PTE_USER) | PTE_P;
					*create = create_rep;
					
					err = 0;
			   }

#ifdef K_MEM_DEBUG
			   kprintf("CREATED");
#endif
		  }
		  else err = -E_FAULT;

		  
	 fa_exit_2:

#ifdef K_MEM_DEBUG
		  kprintf("\n");
#endif

		  release_pte(idx, pte);
	 }

fa_exit:
	 
	 clear_tmp();

#ifdef K_MEM_DEBUG
	 kprintf("[fix_addr] finished err = %d.\n", err);
#endif

	 return err;
}

#undef K_MEM_DEBUG

// 设定的作用是将一个内存段按照 4M 和 4K 的大小分割
// 返回第一个分割点
uint32_t
split_seg(uint32_t s, uint32_t e)
{
	 if ((s & ((1 << PAGETAB_SHIFT) - 1) == 0) &&
		 (s + PAGETAB_SIZE <= e))
		  return s + PAGETAB_SIZE;
	 else return s + PAGE_SIZE;
}

void
clear_area(void *s, void *e)
{
	 char *tmp;
	 tmp = s;

	 while ((void *)tmp < e) {
		  tmp = (void *)((uintptr_t)tmp | (PAGE_SIZE - 1));
		  fix_addr(tmp ++, NULL);
	 }
}

void *
get_local_tvpd(void)
{
	 return get_tvpd(envs[cur_env].lid);
}

void *
get_local_tvpt(void)
{
	 return get_tvpt(envs[cur_env].lid);
}

void *
get_local_tpage(void)
{
	 return get_tpage(envs[cur_env].lid);
}

struct pcs_t *
get_local_pcs(void)
{
	 return get_pcs(envs[cur_env].lid);
}

void *
get_tvpd(int lid)
{
	 return (void *)(KTMP + (((lid << 2) + KTMP_TVPD_PAGE_OFFSET)<< PAGE_SHIFT));
}

void *
get_tvpt(int lid)
{
	 return (void *)(KTMP + (((lid << 2) + KTMP_TVPT_PAGE_OFFSET) << PAGE_SHIFT));
}

void *
get_tpage(int lid)
{
	 return (void *)(KTMP + (((lid << 2) + KTMP_TPAGE_PAGE_OFFSET) << PAGE_SHIFT));
}

struct pcs_t *
get_pcs(int lid)
{
	 return (void *)(KTMP + (((lid << 2) + KTMP_PCS_PAGE_OFFSET) << PAGE_SHIFT));
}


/* 清除当前运行时空间的临时储存 */
/* 其实没有太大的用处, 调试方便使用 */
void
clear_tmp(void)
{
	 vpt[PAGE_NUM(get_local_tvpd())] = 0;
	 vpt[PAGE_NUM(get_local_tvpt())] = 0;
	 vpt[PAGE_NUM(get_local_tpage())] = 0;
	 tlbflush();
}
